/*
 * @(#)RunXMLToJson.java $version 2010. 8. 11
 *
 * Copyright 2007-2010 NHN Corp. All rights Reserved. 
 * NHN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.nhncorp.usf.test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.codehaus.jackson.map.ObjectMapper;

import com.nhncorp.usf.core.Constants;
import com.nhncorp.usf.core.config.runtime.Action;
import com.nhncorp.usf.core.config.runtime.BusinessObject;
import com.nhncorp.usf.core.config.runtime.Method;
import com.nhncorp.usf.core.config.runtime.PageInfo;
import com.nhncorp.usf.core.config.runtime.ParameterInfo;
import com.nhncorp.usf.core.config.runtime.Service;
import com.nhncorp.usf.core.parser.ConfigDOMBuilder;
import com.nhncorp.usf.core.service.ServiceType;

/**
 * Run.xml 을 json 으로 바꿔서 저장한다.
 * 
 * @author Web Platform Development Team
 */
public class RunXMLToJson {
    /**
     * @param args
     *            파라미터<br/>
     *            arg[0] run.xml 이 있는 templates root 경로<br/>
     *            arg[1] null 또는 "all" 일 경우 모든 파일, 특정 파일명일 경우 해당파일만 (특정 파일명은
     *            root 이하 상대 경로)
     * */
    public static void main(String... args) {
        String rootFilePath;
        String fileName = null;

        if (args.length < 1) {
            System.out
                    .println("Usage : RunXMLToJson rootFilePath [file path (null or 'all' for all run.xml / file path for one file)]");
            return;
        }

        rootFilePath = args[0];

        if (args.length >= 2) {
            fileName = args[1];
        }

        if (fileName == null || "all".equals(fileName)) {
            Iterator<File> iterator = getFileIterator(rootFilePath);

            while (iterator.hasNext()) {
                File file = iterator.next();
                parseAndSave(
                        rootFilePath,
                        FilenameUtils.separatorsToSystem(file.getAbsolutePath()));
            }
        } else {
            parseAndSave(rootFilePath, fileName);
        }
    }

    /**
     * @param rootFilePath
     *            run.xml 이 위치한 root path
     * @return {@code rootFilePath} 이하의 run.xml 을 모두 반환한다.
     * */
    @SuppressWarnings("unchecked")
    private static Iterator<File> getFileIterator(String rootFilePath) {
        IOFileFilter fileFilter = FileFilterUtils
                .suffixFileFilter(Constants.RUNTIME_XML);
        IOFileFilter dirFilter = FileFilterUtils.trueFileFilter();
        Collection<File> configFiles = FileUtils.listFiles(new File(
                rootFilePath), fileFilter, dirFilter);
        Iterator<File> iterator = configFiles.iterator();
        return iterator;
    }

    /**
     * {@code fileName} run.xml 을 json 으로 파싱해서 저장한다.
     * 
     * @param rootFilePath
     *            run.xml 이 위치한 root path
     * @param fileName
     *            파싱할 파일 명 ({@code rootFilePath} 폴더 이하의 경로 표시
     *            ex./folder/test_run.xml)
     * */
    private static void parseAndSave(String rootFilePath, String fileName) {
        RunXMLToJson runXmlToJson = new RunXMLToJson();
        Map<String, Object> jsonObj = runXmlToJson.runXmlToJson(rootFilePath,
                fileName);

        if (jsonObj != null) {
            runXmlToJson.saveJson(fileName, jsonObj);
        }
    }

    /**
     * @param rootFilePath
     *            run.xml 이 위치한 root path
     * @param fileName
     *            파싱할 파일 명 ({@code rootFilePath} 폴더 이하의 경로 표시
     *            ex./folder/test_run.xml)
     * @return fileName 을 파싱한 json
     * */
    public Map<String, Object> runXmlToJson(String rootFilePath, String fileName) {
        ConfigDOMBuilder builder = new ConfigDOMBuilder();
        File file = new File(fileName);
        FileInputStream fis = null;

        try {
            fis = new FileInputStream(file);
            PageInfo pageInfo = builder.build(fis, rootFilePath);

            if (pageInfo == null) {
                return null;
            }

            String url = fileName.replace(rootFilePath, "");
            url = url.replace("\\", "/");
            url = url.replace(Constants.RUNTIME_XML, ".json");

            Map<String, Object> jsonRoot = new HashMap<String, Object>();
            jsonRoot.put("url", url);

            return pageInfoToJson(jsonRoot, pageInfo);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return null;
    }

    /**
     * @param jsonRoot
     *            파싱한 Json 을 담을 root
     * @param pageInfo
     *            파싱할 {@link PageInfo}
     * @return 파싱된 {@link JSONObject}
     * */
    public Map<String, Object> pageInfoToJson(Map<String, Object> jsonRoot,
            PageInfo pageInfo) {
        List<Object> actionsJson = new ArrayList<Object>();

        for (Entry<String, Action> entry : pageInfo.getActionInfos().entrySet()) {
            Map<String, Object> actionRootJson = new HashMap<String, Object>();
            Map<String, Object> actionJson = new HashMap<String, Object>();
            actionRootJson.put("action", actionJson);

            actionJson.put("id", entry.getValue().getId());
            List<Object> boJsonArray = new ArrayList<Object>();
            actionJson.put("services", boJsonArray);

            // 병렬
            for (Service service : entry.getValue().getBusinessObjectInfos()) {
                serviceToJson(boJsonArray, new HashMap<String, Object>(),
                        service, -1);
            }

            // 순차
            for (Service service : entry.getValue().getControlBlockInfos()) {
                Map<String, Object> boJson = new HashMap<String, Object>();

                serviceToJson(boJsonArray, boJson, service, 1);

                if (boJson.containsKey("bo")) {
                    boJsonArray.add(boJson);
                }
            }

            actionsJson.add(actionRootJson);
        }

        jsonRoot.put("actions", actionsJson);
        return jsonRoot;
    }

    /**
     * @param boJsonArray
     *            파싱할 {@link JSONObject} 를 넣을 Array
     * @param parent
     *            {@code service} 의 상위 Object
     * @param service
     *            파싱할 {@link Service}
     * @param seq
     *            BO 의 파싱 순서
     * @return BO 가 있을 경우 파싱한 Json, BO 가 없을 경우 null
     * */
    private int serviceToJson(List<Object> boJsonArray,
            Map<String, Object> parent, Service service, int seq) {
        if (ServiceType.bo.equals(service.getServiceType())) {
            BusinessObject bo = (BusinessObject) service;
            Map<String, Object> boJson = new HashMap<String, Object>();
            Map<String, Object> boWrapper = new HashMap<String, Object>();
            boWrapper.put("bo", boJson);

            boJson.put("id", bo.getId());
            boJson.put("seq", (seq > 0 ? seq++ : 0));

            boJsonArray.add(boWrapper);
            parent = boJson;
        } else if (ServiceType.method.equals(service.getServiceType())) {
            Method method = (Method) service;
            Map<String, Object> methodJson = new HashMap<String, Object>();
            parent.put("method", methodJson);
            methodJson.put("id", method.getName());
            methodJson.put("return-id", method.getReturnId());

            List<ParameterInfo> parameters = method.getParameterInfos();
            List<Object> parametersJson = new ArrayList<Object>();
            methodJson.put("params", parametersJson);

            for (ParameterInfo parameter : parameters) {
                Map<String, Object> parameterJson = new HashMap<String, Object>();
                parameterJson.put("id", parameter.getId());
                parameterJson.put("ref", parameter.getRef());
                parameterJson.put("default", parameter.getDefaultValue());
                parametersJson.add(parameterJson);
            }

            parent = methodJson;
        }

        if (service.getServiceInfos().size() > 0) {
            for (Service subService : service.getServiceInfos()) {
                seq = serviceToJson(boJsonArray, parent, subService, seq);
            }
        }

        return seq;
    }

    /**
     * {@code rootFilePath} + {@code fileName} 으로 {@code jsonObj} 를 저장한다.
     * 
     * @param fileName
     *            저장할 파일명
     * @param jsonObj
     *            저장할 {@link JSONObject}
     * */
    private void saveJson(String fileName, Map<String, Object> jsonObj) {
        fileName = FilenameUtils.separatorsToSystem(fileName.replace(
                Constants.RUNTIME_XML, ".json"));
        File file = new File(fileName);
        FileWriter writer = null;

        try {
            writer = new FileWriter(file);
            writer.write(new ObjectMapper().writeValueAsString(jsonObj));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
